from django.db                         import models,transaction
from django.db.models                  import signals
from django.utils.datastructures       import SortedDict
from django.utils.translation          import ugettext_lazy as _
from sitepal.sitebase.managers         import EntityTreeManager
from sitepal.sitebase.models           import EntityTree
from sitepal.sitemenu.models.menus     import Menu
from sitepal.sitemenu.models.menuviews import MenuView
from sitepal.sitemenu.models.util      import get_view_from_path,get_link_parent_from_path
from sitepal.sitemenu.models.util      import get_from_cache_or_load


class MenuLinkManager(EntityTreeManager):
    _cache_threshold = 512
    _expanded_cache  = SortedDict()

    @classmethod
    def _update_cache(cls,sender,instance,created,**kwargs):
        cls._expanded_cache.clear()

    def _lookup_by_name(self,name):
        return self.filter(menu__name__exact=name,expanded=True).exists()

    def has_expanded(self,menu_name=None):
        menu_name = Menu.objects.get_by_name(menu_name).name
        return get_from_cache_or_load(menu_name,self._expanded_cache,self._lookup_by_name,
                                      self._cache_threshold)

class MenuLink(EntityTree):
    title        = models.CharField(_("title"),max_length=100)
    menu         = models.ForeignKey(Menu,verbose_name=_("menu"),editable=False)
    view         = models.ForeignKey(MenuView,verbose_name=_("menu view"),null=True,editable=False)
    path         = models.CharField(_("path"),max_length=200)
    hidden       = models.IntegerField(_("hidden"),default=0)
    external     = models.BooleanField(_("external"),editable=False,default=False)
    has_children = models.BooleanField(_("has children"),editable=False,default=False)
    expanded     = models.BooleanField(_("expanded"),default=False)
    weight       = models.IntegerField(_("weigth"),default=0)
    depth        = models.IntegerField(_("depth"),editable=False,default=0)
    customized   = models.BooleanField(_("customized"),editable=False,default=False)
    p0           = models.IntegerField(editable=False,default=0,db_index=True)
    p1           = models.IntegerField(editable=False,default=0,db_index=True)
    p2           = models.IntegerField(editable=False,default=0,db_index=True)
    p3           = models.IntegerField(editable=False,default=0,db_index=True)
    p4           = models.IntegerField(editable=False,default=0,db_index=True)
    p5           = models.IntegerField(editable=False,default=0,db_index=True)
    p6           = models.IntegerField(editable=False,default=0,db_index=True)
    p7           = models.IntegerField(editable=False,default=0,db_index=True)
    p8           = models.IntegerField(editable=False,default=0,db_index=True)
    p9           = models.IntegerField(editable=False,default=0,db_index=True)
    objects      = MenuLinkManager()

    class Meta(EntityTree.Meta):
        verbose_name        = _('menu link')
        verbose_name_plural = _('menu links')
        unique_together     = ('site','menu','path')
        ordering            = ('p0','p1','p2','p3','p4','p5','p6','p7','p8','p9')
        app_label           = 'sitemenu'

    @property
    def level(self):
        return self.depth - 1

    @property
    def path_ids(self):
        items = []
        for i in xrange(0,self.depth):
            items.append(getattr(self,"p{0}".format(i)))
        return items

    @property
    def path_list(self):
        items = []
        for item_id in self.path_ids:
            items.append(self._default_manager.get(pk=item_id))
        return items

    def __unicode__(self):
        return "{0} {1}".format(".".join(map(lambda i: str(i),self.path_ids)),self.title)

    def _direct_save_(self):
        super(MenuLink,self).save()

    @transaction.commit_on_success
    def save(self,*args,**kwargs):
        self.view    = get_view_from_path(self.path) if not self.external else None
        self.parent  = get_link_parent_from_path(self)
        old_path_ids = self.path_ids
        if self.parent:
            if self.parent.depth >= 10:
                raise ValueError,"Maximum depth of 10 exceeded"
            for i in xrange(0,self.parent.depth):
                name = "p{0}".format(i)
                setattr(self,name,getattr(self.parent,name))
            self.depth = self.parent.depth + 1
            if not self.parent.has_children:
                self.parent.has_children = True
                self.parent.save()
        else:
            self.depth = 1
            for i in xrange(0,10):
                setattr(self,"p{0}".format(i),0)
        if not self.pk:
            super(MenuLink,self).save(*args,**kwargs)
            setattr(self,"p{0}".format(self.depth-1),self.pk)
            super(MenuLink,self).save()
        else:
            setattr(self,"p{0}".format(self.depth-1),self.pk)
            super(MenuLink,self).save(*args,**kwargs)
        new_path_ids = self.path_ids
        # if we have children and the path has changed, we need to reparent the children
        if self.has_children and old_path_ids != new_path_ids:
            qargs        = {}
            old_path_len = len(old_path_ids)
            for i in xrange(0,old_path_len):
                qargs["p{0}".format(i)] = old_path_ids[i]
            if old_path_len < 10:
                qargs["p{0}__gt".format(old_path_len)] = 0
            descendants = self._default_manager.filter(**qargs).all()
            for node in descendants:
                node_path_ids = new_path_ids + node.path_ids[old_path_len:]
                node_path_len = len(node_path_ids)
                for i in xrange(0,node_path_len):
                    setattr(node,"p{0}".format(i),node_path_ids[i])
                node.depth = node_path_len
                node._direct_save_()

signals.post_save.connect(MenuLinkManager._update_cache,sender=MenuLink)

